package com.atguigu.gulimall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.gulimall.product.service.CategoryBrandRelationService;
import com.atguigu.gulimall.product.vo.Catelog2Vo;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.atguigu.common.utils.PageUtils;
import com.atguigu.common.utils.Query;

import com.atguigu.gulimall.product.dao.CategoryDao;
import com.atguigu.gulimall.product.entity.CategoryEntity;
import com.atguigu.gulimall.product.service.CategoryService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    @Autowired
    CategoryBrandRelationService categoryBrandRelationService;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private RedissonClient redisson;
    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public List<CategoryEntity> listWithTree() {
        List<CategoryEntity> entities = this.baseMapper.selectList(null);
        List<CategoryEntity> catLevel1 = entities.stream().filter(categoryEntity -> categoryEntity.getParentCid() == 0)
                .map(menu -> {
                    menu.setChildren(getChildrens(menu, entities));
                    return menu;
                })
                .sorted(Comparator.comparingInt(menu -> (menu.getSort() == null ? 0 : menu.getSort())))
                .collect(Collectors.toList());

        return catLevel1;
    }

    @Override
    public void removeMenuByIds(List<Long> ids) {
        //TODO
        //逻辑删除
        baseMapper.deleteBatchIds(ids);
    }

    @Override
    public List<Long> findCategoryPath(Long catelogId) {
        List<Long> path = new LinkedList<>();
        findParentPath(catelogId, path);
        return path;
    }

    @Caching(evict = {
            @CacheEvict(cacheNames = "category",key = "'getLevel1Categorys'"),
            @CacheEvict(cacheNames = "category",key = "'getCatelogJson'")
    })
    @Transactional
    @Override
    public void updateCascade(CategoryEntity category) {
        this.updateById(category);
        String categoryName = category.getName();
        if (!StringUtils.isEmpty(categoryName)){
            categoryBrandRelationService.updateCategory(category.getCatId(), categoryName);
        }
    }

    @Cacheable(cacheNames = "category",key = "#root.method.name")
    @Override
    public List<CategoryEntity> getLevel1Categorys() {
        System.out.println("getLevel1Categorys");
        return this.baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));
    }

    @Cacheable(cacheNames = "category",key = "#root.methodName",sync = true)
    @Override
    public Map<String, List<Catelog2Vo>> getCatelogJson(){
        return getCatelogJsonFromDb();
    }

    public Map<String, List<Catelog2Vo>> getCatelogJsonWithCodeChache() {
        String catelogJson = stringRedisTemplate.opsForValue().get("catelogJson");
        if (StringUtils.isEmpty(catelogJson)){
            RLock rLock = redisson.getLock("catelogjson_lock");
            rLock.lock();
            Map<String, List<Catelog2Vo>> catelogJsonFromDb;
            try {
                catelogJsonFromDb = getCatelogJsonFromDb();
                stringRedisTemplate.opsForValue().set("catelogJson", JSON.toJSONString(catelogJsonFromDb));
                return catelogJsonFromDb;
            }finally {
                rLock.unlock();
            }

        }
        return JSON.parseObject(catelogJson,new TypeReference<Map<String, List<Catelog2Vo>>>(){});
    }

    //TODO 堆外内存溢出
    // redisTemplate 使用 letture letture使用 netty
    //若虚拟机参数没有设置-Dio.netty.maxDirectoryMemory则默认使用-Xmx
    //若设置了还是会出现堆外内存溢出
    //解决方案:1 升级letture 2 使用jedis
    public Map<String, List<Catelog2Vo>> getCatelogJsonWithNonLock() {
        String catelogJson = stringRedisTemplate.opsForValue().get("catelogJson");
        if (StringUtils.isEmpty(catelogJson)){
            Map<String, List<Catelog2Vo>> catelogJsonFromDb = getCatelogJsonFromDb();
            stringRedisTemplate.opsForValue().set("catelogJson", JSON.toJSONString(catelogJsonFromDb));
            return catelogJsonFromDb;
        }else {
            return JSON.parseObject(catelogJson,new TypeReference<Map<String, List<Catelog2Vo>>>(){});
        }
    }

    public Map<String, List<Catelog2Vo>> getCatelogJsonFromDb() {
        List<CategoryEntity> categorys = this.baseMapper.selectList(null);
        Map<String, List<Catelog2Vo>> map = categorys.stream().filter(c -> c.getParentCid() == 0).collect(Collectors.toMap(categoryl1 -> categoryl1.getCatId().toString(), categoryl1 -> {
            Long categoryl1Id = categoryl1.getCatId();
            List<CategoryEntity> categoryl2s = getCategorysByParentCid(categorys, categoryl1Id);
            if (categoryl2s == null)
                return null;
            List<Catelog2Vo> catelog2Vos = categoryl2s.stream().map(categoryl2 -> {
                List<CategoryEntity> categoryl3s = getCategorysByParentCid(categorys, categoryl2.getCatId());
                if (categoryl3s == null)
                    return null;
                List<Catelog2Vo.Catalog3Vo> catalog3VoList = categoryl3s.stream().map(categoryl3 -> {
                    return new Catelog2Vo.Catalog3Vo(categoryl2.getCatId().toString(), categoryl3.getCatId().toString(), categoryl3.getName());
                }).collect(Collectors.toList());
                return new Catelog2Vo(categoryl1Id.toString(), catalog3VoList, categoryl2.getCatId().toString(), categoryl2.getName());
            }).collect(Collectors.toList());
            return catelog2Vos;
        }));
        return map;
    }

    private List<CategoryEntity> getCategorysByParentCid( List<CategoryEntity> categorys ,Long parentCid){
        return categorys.stream().filter(c-> c.getParentCid()==parentCid).collect(Collectors.toList());
    }


    private void findParentPath(Long catelogId, List<Long> path) {
        path.add(0,catelogId);
        Long parentCid = this.getById(catelogId).getParentCid();
        if (parentCid != 0L) {
            findParentPath(parentCid, path);
        }
    }


    private List<CategoryEntity> getChildrens(CategoryEntity root, List<CategoryEntity> all) {
        List<CategoryEntity> collect = all.stream().filter(categoryEntity -> categoryEntity.getParentCid().equals(root.getCatId()))
                .map(categoryEntity -> {
                    categoryEntity.setChildren(getChildrens(categoryEntity, all));
                    return categoryEntity;
                })
                .sorted(Comparator.comparingInt(categoryEntity -> (categoryEntity.getSort() == null ? 0 : categoryEntity.getSort())))
                .collect(Collectors.toList());
        return collect;

    }
}